﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  internal sealed class FilterEnumerator<T> : AsyncParentEnumerator<T> where T : RawDataItem
  {
    #region Static Fields

    private const int MaximumMoveCount = 1000;

    private static readonly Guid CountCallId = DataUtils.CreateCallId();
    private static readonly Guid GroupCountCallId = DataUtils.CreateCallId();
    private static readonly Guid StatCallId = DataUtils.CreateCallId();
    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();
    private static readonly Guid GetFilteredItemsCallId = DataUtils.CreateCallId();
    private static readonly Guid MoveAndFetchCallId = DataUtils.CreateCallId();
    private static readonly Guid FetchItemsCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal FilterEnumerator(
      IAsyncEnumerator childEnumerator,
      DataSourceProvider dataSourceProvider,
      IContextualConvertHelper<object, T> convertHelper )
      : base( childEnumerator )
    {
      if( dataSourceProvider == null )
        throw new ArgumentNullException( "dataSourceProvider" );

      if( convertHelper == null )
        throw new ArgumentNullException( "convertHelper" );

      m_dataSourceProvider = dataSourceProvider;
      m_convertHelper = convertHelper;
    }

    #endregion

    public override IAsyncResult BeginCount( CountParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      Predicate<T> predicate;
      DataSourceContext newContext = this.GetNewContext( parameters.Context, out predicate );

      bool isLocalFilteringEnabled = ( predicate != null );
      CountParameters newParameters;

      if( isLocalFilteringEnabled )
      {
        newParameters = new CountParameters( parameters.StartPosition, parameters.TargetGroup, newContext );
      }
      else
      {
        newParameters = parameters;
      }

      AsyncResult asyncResult = new AsyncResult( FilterEnumerator<T>.CountCallId, userCallback, asyncState );

      asyncResult.Start(
        ( cb, st ) => base.BeginCount( newParameters, cb, st ),
        ( par, iar ) =>
        {
          CountResult result = base.EndCount( iar );

          if( !isLocalFilteringEnabled )
            return new DependentCallResult( result );

          if( ( m_disposed ) || ( result == null ) )
            return new DependentCallResult( null );

          return new DependentCallResult( new CountResult( result.Count.ToEstimate() ) );
        } );

      return asyncResult;
    }

    public override CountResult EndCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FilterEnumerator<T>.CountCallId );

      return asyncResult.GetCallResult<CountResult>();
    }

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback userCallback,
      object asyncState )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      Predicate<T> predicate;
      DataSourceContext newContext = this.GetNewContext( parameters.Context, out predicate );

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult( FilterEnumerator<T>.JumpMoveAndFetchCallId, userCallback, asyncState, parameters );

      // We must remove undesired items from the results.
      if( predicate != null )
      {
        JumpMoveAndFetchParameters newParameters = parameters.CreateWithNewContext( newContext );

        asyncResult.Start(
          ( cb, st ) => this.BeginGetFilteredItems( newParameters, predicate, cb, st ),
          ( par, iar ) => new DependentCallResult( this.EndGetFilteredItems( iar ) ) );
      }
      // There is nothing to filter.
      else
      {
        asyncResult.Start(
          ( cb, st ) => base.BeginJumpMoveAndFetch( parameters, cb, st ),
          ( par, iar ) => new DependentCallResult( base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar ) ) );
      }

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<T> EndJumpMoveAndFetch( JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FilterEnumerator<T>.JumpMoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    public override IAsyncResult BeginGroupCount( CountParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      Predicate<T> predicate;
      DataSourceContext newContext = this.GetNewContext( parameters.Context, out predicate );

      bool isLocalFilteringEnabled = ( predicate != null );
      CountParameters newParameters;

      if( isLocalFilteringEnabled )
      {
        newParameters = new CountParameters( parameters.StartPosition, parameters.TargetGroup, newContext );
      }
      else
      {
        newParameters = parameters;
      }

      AsyncResult asyncResult = new AsyncResult( FilterEnumerator<T>.GroupCountCallId, userCallback, asyncState );

      asyncResult.Start(
        ( cb, st ) => base.BeginGroupCount( newParameters, cb, st ),
        ( par, iar ) =>
        {
          GroupCountResult result = base.EndGroupCount( iar );

          if( !isLocalFilteringEnabled )
            return new DependentCallResult( result );

          if( ( m_disposed ) || ( result == null ) )
            return new DependentCallResult( null );

          List<EstimateInt64> groupCounts = ( from count in result.GroupCounts
                                              select count.ToEstimate() ).ToList();
          return new DependentCallResult( new GroupCountResult( groupCounts ) );
        } );

      return asyncResult;
    }

    public override GroupCountResult EndGroupCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FilterEnumerator<T>.GroupCountCallId );

      return asyncResult.GetCallResult<GroupCountResult>();
    }

    public override IAsyncResult BeginGetStatValues( StatParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      Predicate<T> predicate;
      DataSourceContext newContext = this.GetNewContext( parameters.Context, out predicate );

      bool isLocalFilteringEnabled = ( predicate != null );
      StatParameters newParameters;

      if( isLocalFilteringEnabled )
      {
        newParameters = new StatParameters( parameters.StatFunctions, parameters.TargetPath, newContext );
      }
      else
      {
        newParameters = parameters;
      }

      AsyncResult asyncResult = new AsyncResult( FilterEnumerator<T>.StatCallId, userCallback, asyncState );

      asyncResult.Start(
        ( cb, st ) => base.BeginGetStatValues( newParameters, cb, st ),
        ( par, iar ) =>
        {
          StatResults result = base.EndGetStatValues( iar );

          if( !isLocalFilteringEnabled )
            return new DependentCallResult( result );

          if( ( m_disposed ) || ( result == null ) )
            return new DependentCallResult( null );

          List<StatResult> statResults = ( from statResult in result.Results
                                           select ( statResult != null ) ? statResult.ToEstimate() : null ).ToList();
          return new DependentCallResult( new StatResults( statResults ) );
        } );

      return asyncResult;
    }

    public override StatResults EndGetStatValues( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FilterEnumerator<T>.StatCallId );

      return asyncResult.GetCallResult<StatResults>();
    }

    protected override IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgs(
      DataSourceContext context,
      DataSourceChangedEventArgs e )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( e == null )
        throw new ArgumentNullException( "e" );

      lock( this.SyncRoot )
      {
        if( m_disposed )
          Enumerable.Empty<DataSourceChangedEventArgs>();

        Predicate<T> predicate;
        DataSourceContext newContext = this.GetNewContext( context, out predicate );
        IEnumerable<DataSourceChangedEventArgs> childEventArgs = base.TransformDataSourceChangedEventArgs( newContext, e );

        if( predicate == null )
          return childEventArgs;

        List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>();

        foreach( DataSourceChangedEventArgs ev in childEventArgs )
        {
          switch( ev.Action )
          {
            case DataSourceChangedAction.Add:
              newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsAdded( ev, predicate ) );
              break;

            case DataSourceChangedAction.Remove:
              newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsRemoved( ev, predicate ) );
              break;

            case DataSourceChangedAction.ItemChanged:
              newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsChanged( ev ) );
              break;

            case DataSourceChangedAction.Replace:
              newEventArgs.AddRange( this.TransformDataSourceChangedEventArgsItemsReplaced( ev, predicate ) );
              break;

            case DataSourceChangedAction.Reset:
              newEventArgs.Add( ev );
              break;

            default:
              throw new NotSupportedException();
          }
        }

        return newEventArgs;
      }
    }

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;

      base.Dispose( disposing );
    }

    private static Predicate<T> GetPredicate(
      FilterExpressionBase filterExpression,
      DataSourceProvider dataSourceProvider )
    {
      if( filterExpression == null )
        throw new ArgumentNullException( "filterExpression" );

      if( dataSourceProvider == null )
        throw new ArgumentNullException( "dataSourceProvider" );

      PredicateBuilder pb = new PredicateBuilder( filterExpression );
      Func<object, bool> predicate = pb.ToPredicate<object>( dataSourceProvider.ElementType, dataSourceProvider.GetElementMemberType );

      return ( item ) => predicate.Invoke( item );
    }

    private static Predicate<T> GetPredicate( Predicate<object> predicate, DataSourceContext context, IContextualConvertHelper<object, T> convertHelper )
    {
      if( predicate == null )
        throw new ArgumentNullException( "predicate" );

      if( convertHelper == null )
        throw new ArgumentNullException( "convertHelper" );

      return
        ( item ) =>
        {
          object userObject = convertHelper.ConvertBack( context, item );

          return predicate.Invoke( userObject );
        };
    }

    private static Predicate<T> Combine( Predicate<T> predicateA, Predicate<T> predicateB )
    {
      if( predicateA == null )
        throw new ArgumentNullException( "predicateA" );

      if( predicateB == null )
        throw new ArgumentNullException( "predicateB" );

      return ( item ) => predicateA.Invoke( item ) && predicateB.Invoke( item );
    }

    private DataSourceContext GetNewContext( DataSourceContext context, out Predicate<T> predicate )
    {
      predicate = null;

      if( context == null )
        throw new ArgumentNullException( "context" );

      DataSourceFilterContext filterContext = context.FilterContext;
      bool canFilterByFilterExpression = m_dataSourceProvider.CanFilterBy( filterContext.FilterExpression );

      // Transform the FilterExpression into a Predicate<T> if the data source cannot handle it.
      if( !canFilterByFilterExpression )
      {
        predicate = FilterEnumerator<T>.GetPredicate( filterContext.FilterExpression, m_dataSourceProvider );
      }

      bool canFilterByPredicate = m_dataSourceProvider.CanFilterBy( filterContext.Filter );

      // Transform the Predicate<object> into a Predicate<T> if the data source cannot handle it.
      if( !canFilterByPredicate )
      {
        Predicate<T> currentPredicate = FilterEnumerator<T>.GetPredicate( filterContext.Filter, context, m_convertHelper );

        // Merge the two predicates.
        if( predicate != null )
        {
          predicate = FilterEnumerator<T>.Combine( currentPredicate, predicate );
        }
        else
        {
          predicate = currentPredicate;
        }
      }

      // The current enumerator doesn't do anything.  We may keep
      // the current context as it is.
      if( canFilterByFilterExpression && canFilterByPredicate )
        return context;

      // Prepare the new context to be use by the underlying enumerator.
      DataSourceFilterContext newFilterContext;

      if( !canFilterByFilterExpression && !canFilterByPredicate )
      {
        newFilterContext = new DataSourceFilterContext( null, null );
      }
      else if( !canFilterByFilterExpression )
      {
        newFilterContext = new DataSourceFilterContext( filterContext.Filter, null );
      }
      else
      {
        newFilterContext = new DataSourceFilterContext( null, filterContext.FilterExpression );
      }

      return new DataSourceContext(
        context.GroupContext,
        context.SortContext,
        newFilterContext,
        context.PrimaryKeys );
    }

    private JumpMoveAndFetchAsyncResult BeginGetFilteredItems( JumpMoveAndFetchParameters parameters, Predicate<T> predicate, AsyncCallback callback, object state )
    {
      if( predicate == null )
        throw new ArgumentNullException( "predicate" );

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult( FilterEnumerator<T>.GetFilteredItemsCallId, callback, state, parameters );

      if( ( parameters.ExpectedMoveCount <= 0 && parameters.SourcePosition.IsBeforeFirst )
        || ( parameters.ExpectedMoveCount >= 0 && parameters.SourcePosition.IsAfterLast ) )
      {
        RelativeOffset relativeOffset;

        if( parameters.SourcePosition.IsBeforeFirst )
        {
          relativeOffset = new RelativeOffset( 0, ReferencePoint.BeforeFirst );
        }
        else
        {
          relativeOffset = new RelativeOffset( 0, ReferencePoint.AfterLast );
        }

        asyncResult.StartSync(
          new JumpMoveAndFetchResult<T>(
            asyncResult,
            0,
            null,
            false,
            parameters.SourcePosition,
            relativeOffset,
            null ) );
      }
      else if( parameters.ExpectedMoveCount == 0 )
      {
        asyncResult.Start(
          ( cb, st ) => this.BeginFetchItems( parameters, predicate, cb, st ),
          ( par, iar ) => new DependentCallResult( this.EndFetchItems( iar ) ) );
      }
      else
      {
        // We must transform the move into a fetch in order filter out the undesired items.
        asyncResult.Start(
          ( cb, st ) => this.BeginMoveAndFetch( parameters, predicate, cb, st ),
          ( par, iar ) => new DependentCallResult( this.EndMoveAndFetch( iar ) ) );
      }

      return asyncResult;
    }

    private JumpMoveAndFetchResult<T> EndGetFilteredItems( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FilterEnumerator<T>.GetFilteredItemsCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    private JumpMoveAndFetchAsyncResult BeginMoveAndFetch( JumpMoveAndFetchParameters parameters, Predicate<T> predicate, AsyncCallback callback, object state )
    {
      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult( FilterEnumerator<T>.MoveAndFetchCallId, callback, state, parameters );
      long expectedMoveCount = parameters.ExpectedMoveCount;
      int expectedFetchCount = parameters.ExpectedFetchCount;
      long remainingMoveCount = Math.Abs( expectedMoveCount );
      int moveDirection = DataUtils.GetUnit( expectedMoveCount );
      bool approximativeMove = parameters.ApproximateMove;
      SourcePosition startPosition = parameters.SourcePosition;
      DataSourceContext context = parameters.Context;
      bool firstOperation = true;

      // This anonymous method start querying for the desired items once the move part
      // is completed.
      Func<AsyncCallback, object, IAsyncResult> beginFetchItems =
        ( cb, st ) =>
        {
          int fetchCount = expectedFetchCount;

          if( fetchCount == 0 )
          {
            fetchCount = moveDirection;
          }

          JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                       startPosition,
                                                       0,
                                                       fetchCount,
                                                       false,
                                                       context );

          return this.BeginFetchItems( newParameters, predicate, cb, st );
        };

      // This anonymous method returns the fetched items of the whole operation.
      Func<AsyncResult, IAsyncResult, DependentCallResult> endFetchItems =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> fetchResult = this.EndFetchItems( iar );

          if( ( m_disposed ) || ( fetchResult == null ) )
            return new DependentCallResult( null );

          ICollection<T> fetchedItems;

          if( expectedFetchCount == 0 )
          {
            if( fetchResult.Result )
            {
              fetchedItems = new List<T>( 0 );
            }
            else
            {
              fetchedItems = null;
            }
          }
          else
          {
            fetchedItems = fetchResult.FetchedItems;
          }

          return new DependentCallResult(
            new JumpMoveAndFetchResult<T>(
              asyncResult,
              expectedMoveCount,
              fetchedItems,
              fetchResult.Result,
              fetchResult.EndPositionObject,
              fetchResult.EndPositionOffset,
              fetchResult.TotalCount ) );
        };

      // This anonymous method fetches items in order to move as expected.
      Func<AsyncCallback, object, IAsyncResult> beginMoveMore =
        ( cb, st ) =>
        {
          long moveCount;
          int fetchCount;

          if( approximativeMove )
          {
            moveCount = expectedMoveCount;
            fetchCount = 0;

            Debug.Assert( moveCount != 0 );
          }
          else
          {
            if( startPosition.IsOutOfBound )
            {
              moveCount = moveDirection;
              remainingMoveCount--;
            }
            else
            {
              moveCount = ( firstOperation ) ? 0 : moveDirection;
            }

            // We fetch an extra item in order to identify the next starting position.
            fetchCount = Convert.ToInt32( Math.Max( 1, Math.Min( remainingMoveCount + 1, FilterEnumerator<T>.MaximumMoveCount ) ) ) * moveDirection;
          }

          JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                       startPosition,
                                                       moveCount,
                                                       fetchCount,
                                                       false,
                                                       context );

          return this.BeginFetchItems( newParameters, predicate, cb, st );
        };

      Func<AsyncResult, IAsyncResult, DependentCallResult> endMoveMore = null;
      endMoveMore =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> moveResult = this.EndFetchItems( iar );

          if( ( m_disposed ) || ( moveResult == null ) )
            return new DependentCallResult( null );

          firstOperation = false;

          if( moveResult.FetchedItems != null )
          {
            long actualMoveCount = moveResult.FetchedItems.Count;

            // We must remove the extra item that was fetch in order to identity
            // the next starting position.
            if( ( actualMoveCount > 0 ) && ( moveResult.Result ) )
            {
              actualMoveCount--;
            }

            remainingMoveCount -= actualMoveCount;

            Debug.Assert( remainingMoveCount >= 0 );
          }
          // An adjustment must be done if the data source is empty.
          else if( ( remainingMoveCount > 0 ) && ( moveResult.SourcePosition != moveResult.EndPositionObject ) )
          {
            remainingMoveCount--;
          }

          // There are no more items in the source.
          if( !moveResult.Result )
          {
            long actualMoveCount = ( Math.Abs( expectedMoveCount ) - remainingMoveCount ) * moveDirection;

            return new DependentCallResult(
              new JumpMoveAndFetchResult<T>(
                asyncResult,
                actualMoveCount,
                null,
                moveResult.Result,
                moveResult.EndPositionObject,
                moveResult.EndPositionOffset,
                moveResult.TotalCount.ToEstimate() ) );
          }

          // Set the starting position of the next operation.
          startPosition = moveResult.EndPositionObject;

          // The move part is done.
          if( remainingMoveCount == 0 )
            return new DependentCallResult( null, beginFetchItems, endFetchItems );

          // The move part isn't done yet, continue.
          return new DependentCallResult( null, beginMoveMore, endMoveMore );
        };

      asyncResult.Start( beginMoveMore, endMoveMore );

      return asyncResult;
    }

    private JumpMoveAndFetchResult<T> EndMoveAndFetch( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FilterEnumerator<T>.MoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    private JumpMoveAndFetchAsyncResult BeginFetchItems( JumpMoveAndFetchParameters parameters, Predicate<T> predicate, AsyncCallback callback, object state )
    {
      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult( FilterEnumerator<T>.FetchItemsCallId, callback, state, parameters );
      long initialMoveCount = parameters.ExpectedMoveCount;
      int remainingFetchCount = Math.Abs( parameters.ExpectedFetchCount );
      int fetchDirection = DataUtils.GetUnit( parameters.ExpectedFetchCount );
      SourcePosition startPosition = parameters.SourcePosition;
      DataSourceContext context = parameters.Context;
      bool firstOperation = true;
      Nullable<long> actualMoveCount = null;
      List<T> fetchedItems = new List<T>();

      Func<AsyncCallback, object, IAsyncResult> getMore =
        ( cb, st ) =>
        {
          long moveCount;

          if( firstOperation )
          {
            moveCount = initialMoveCount;
          }
          else
          {
            moveCount = fetchDirection;
          }

          int fetchCount = remainingFetchCount * fetchDirection;

          Debug.Assert( fetchCount != 0 );

          JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                       startPosition,
                                                       moveCount,
                                                       fetchCount,
                                                       false,
                                                       context );

          return base.BeginJumpMoveAndFetch( newParameters, cb, st );
        };

      Func<AsyncResult, IAsyncResult, DependentCallResult> retrieveItems = null;
      retrieveItems =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> fetchResult = base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );

          if( ( m_disposed ) || ( fetchResult == null ) )
            return new DependentCallResult( null );

          if( firstOperation )
          {
            actualMoveCount = fetchResult.ActualMoveCount;
            firstOperation = false;
          }

          if( fetchResult.FetchedItems != null )
          {
            T[] items = fetchResult.FetchedItems.ToArray();
            T[] validItems;

            if( predicate == null )
            {
              validItems = items;
            }
            else
            {
              validItems = items.Where( ( item ) => predicate.Invoke( item ) ).ToArray();
            }

            fetchedItems.AddRange( validItems );
            remainingFetchCount -= validItems.Length;

            Debug.Assert( remainingFetchCount >= 0 );
          }

          // There are no more items in the source.
          if( !fetchResult.Result )
            return new DependentCallResult(
              new JumpMoveAndFetchResult<T>(
                asyncResult,
                actualMoveCount,
                ( fetchedItems.Count != 0 ) ? fetchedItems : null,
                fetchResult.Result,
                fetchResult.EndPositionObject,
                fetchResult.EndPositionOffset,
                fetchResult.TotalCount.ToEstimate() ) );

          // We have all the desired items.
          if( remainingFetchCount == 0 )
            return new DependentCallResult(
              new JumpMoveAndFetchResult<T>(
                asyncResult,
                actualMoveCount,
                fetchedItems,
                fetchResult.Result,
                fetchResult.EndPositionObject,
                fetchResult.EndPositionOffset.ToEstimate(),
                fetchResult.TotalCount.ToEstimate() ) );

          // Set the starting position of the next operation.
          startPosition = fetchResult.EndPositionObject;

          // Retrieve the remaining items.
          return new DependentCallResult( null, getMore, retrieveItems );
        };

      asyncResult.Start( getMore, retrieveItems );

      return asyncResult;
    }

    private JumpMoveAndFetchResult<T> EndFetchItems( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FilterEnumerator<T>.FetchItemsCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsAdded(
      DataSourceChangedEventArgs e,
      Predicate<T> predicate )
    {
      Debug.Assert( e.Action == DataSourceChangedAction.Add );

      if( ( e.NewItems == null ) || ( e.NewItems.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      if( predicate == null )
        return new DataSourceChangedEventArgs[] { e };

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>( 1 );
      List<object> items = ( from item in e.NewItems.Cast<T>()
                             where predicate.Invoke( item )
                             select ( object )item ).ToList();

      if( items.Count == e.NewItems.Count )
      {
        newEventArgs.Add( e );
      }
      else if( items.Count > 0 )
      {
        newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Add, items, null ) );
      }

      return newEventArgs;
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsRemoved(
      DataSourceChangedEventArgs e,
      Predicate<T> predicate )
    {
      Debug.Assert( e.Action == DataSourceChangedAction.Remove );

      if( ( e.OldItems == null ) || ( e.OldItems.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      if( predicate == null )
        return new DataSourceChangedEventArgs[] { e };

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>( 1 );
      List<object> items = ( from item in e.OldItems.Cast<T>()
                             where predicate.Invoke( item )
                             select ( object )item ).ToList();

      if( items.Count == e.OldItems.Count )
      {
        newEventArgs.Add( e );
      }
      else if( items.Count > 0 )
      {
        newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Remove, null, items ) );
      }

      return newEventArgs;
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsReplaced(
      DataSourceChangedEventArgs e,
      Predicate<T> predicate )
    {
      Debug.Assert( e.Action == DataSourceChangedAction.Replace );
      Debug.Assert( e.OldItems.Count == e.NewItems.Count );

      if( e.OldItems.Count == 0 )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      if( predicate == null )
        return new DataSourceChangedEventArgs[] { e };

      List<DataSourceChangedEventArgs> newEventArgs = new List<DataSourceChangedEventArgs>();
      List<object> itemsAdded = new List<object>();
      List<object> itemsRemoved = new List<object>();
      List<object> itemsReplacedOld = new List<object>();
      List<object> itemsReplacedNew = new List<object>();
      int count = e.NewItems.Count;

      for( int i = 0; i < count; i++ )
      {
        T oldItem = ( T )e.OldItems[ i ];
        T newItem = ( T )e.NewItems[ i ];

        bool isOldItemAccepted = true;
        bool isNewItemAccepted = true;

        if( predicate != null )
        {
          isOldItemAccepted = predicate.Invoke( oldItem );
          isNewItemAccepted = predicate.Invoke( newItem );
        }

        // The item has been replaced.
        if( isOldItemAccepted && isNewItemAccepted )
        {
          itemsReplacedOld.Add( oldItem );
          itemsReplacedNew.Add( newItem );
        }
        // The item has been removed.
        else if( isOldItemAccepted )
        {
          itemsRemoved.Add( oldItem );
        }
        // The item has been added.
        else if( isNewItemAccepted )
        {
          itemsAdded.Add( newItem );
        }
      }

      if( itemsReplacedOld.Count == count )
      {
        newEventArgs.Add( e );
      }
      else
      {
        if( itemsReplacedOld.Count > 0 )
        {
          newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Replace, itemsReplacedNew, itemsReplacedOld ) );
        }

        if( itemsAdded.Count > 0 )
        {
          newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Add, itemsAdded, null ) );
        }

        if( itemsRemoved.Count > 0 )
        {
          newEventArgs.Add( new DataSourceChangedEventArgs( DataSourceChangedAction.Remove, null, itemsRemoved ) );
        }
      }

      return newEventArgs;
    }

    private IEnumerable<DataSourceChangedEventArgs> TransformDataSourceChangedEventArgsItemsChanged(
      DataSourceChangedEventArgs e )
    {
      Debug.Assert( e.Action == DataSourceChangedAction.ItemChanged );

      if( ( e.ItemsChanged == null ) || ( e.ItemsChanged.Count == 0 ) )
        return Enumerable.Empty<DataSourceChangedEventArgs>();

      return new DataSourceChangedEventArgs[] { e };
    }

    #region Private Fields

    private readonly DataSourceProvider m_dataSourceProvider;
    private readonly IContextualConvertHelper<object, T> m_convertHelper;
    private bool m_disposed; //false

    #endregion
  }
}
